package com.olt.server.oltCenter.oltTestService;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.olt.dao.oltCenter.IExamPaper;
import com.olt.dao.oltCenter.IExamProblem;
import com.olt.dao.oltTest.*;
import com.olt.dao.user.ICustomDao;
import com.olt.domain.oltCenter.problem.*;
import com.olt.domain.oltTest.*;

import com.olt.oj.problems.*;
import com.olt.ojdomain.Solution;
import com.olt.ojdomain.SourceCode;
import com.olt.result.ErrorEnum;
import com.olt.result.PageResult;
import com.olt.server.dto.PageInput;
import com.olt.server.dto.SelectDto;
import com.olt.server.oltCenter.oltTestService.dto.*;
import org.hsqldb.lib.FileUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import util.FtpUtil;
import util.ResourcePath;
import util.SM2Util;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;


@Service
@Transactional(rollbackForClassName="RuntimeException")
public class OltTestService {
    @Autowired
    IOltProblemGroup problemGroup;

    @Autowired
    IOJProblem iojProblem;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    IOltProblem oltProblem;

    @Autowired
    ISolution iSolution;

    @Autowired
    ISourceCode iSourceCode;

    @Autowired
    IRuntimeinfo iRuntimeinfo;

    @Autowired
    ICompileinfo iCompileinfo;

    @Autowired
    IExamPaper iExamPaper;

    @Autowired
    IExamProblem iExamProblem;


    @Autowired
    IOltTest iOltTest;


    @Autowired
    IUserScoreTotal iUserScoreTotal;

    @Autowired
    IUserExamScore iUserExamScore;

    @Autowired
    ICustomDao iCustomDao;



    public Page<OltProblemGroup> getOltProblemsGroup(PageInput input,Long userId){
        var page= PageRequest.of(input.getCurrentPage(), input.getSize());
        return problemGroup.findAllByCreateUserOrderByDateDesc(userId,page);
    }

    public void saveOltProblemGroup(ProblemGroupInput input,Long userId){
        var group=new OltProblemGroup();
        if(input.getGroupId()==null){
            group.setProblemGroupId(IdUtil.simpleUUID());
            group.setDate(new Date());
            group.setCreateUser(userId);
            mongoTemplate.createCollection(group.getProblemGroupId().toString());
        }
        else {
            group=problemGroup.getOne(input.getGroupId());

        }
        group.setGroupName(input.getGroupName());
        group.setIsOpen(input.getGroupOpen());

        problemGroup.save(group);

    }

    public void deleteProblemGroup(List<String> uuids){
        problemGroup.deleteAllByProblemGroupIdIn(uuids);
        for(var i:uuids) {
            mongoTemplate.dropCollection(i.toString());
            String path= ResourcePath.getResInstance().ProblemResource+ File.separator+i.toString();
            FileUtil.getFileUtil().delete(path);
        }
    }

    public ProblemGroupInput getProblemGroupForEdit(String id){
        var one=problemGroup.getOne(id);
        var res=new ProblemGroupInput();
        res.setGroupId(one.getProblemGroupId());
        res.setGroupName(one.getGroupName());
        res.setGroupOpen(one.getIsOpen());
        return res;
    }


    public <T extends ProblemAbstract> Boolean  saveProblem(T problem){
        try {
            if(problem.getProblem().getProblemId()==null) {
                var id = IdUtil.simpleUUID();
                problem.getProblem().setProblemId(id);
                problem.getProblem().setDate(new Date());
            }

            problem.setId(problem.getProblem().getProblemId());

            oltProblem.save(problem.getProblem());

            mongoTemplate.save(problem, problem.getProblem().getGroupId());



            return true;
        }
        catch (Exception e){
            return false;
        }
    }

    public PageResult<ProblemAbstract> getOltProblems(GetProblemInput input){
        var page= PageRequest.of(input.getCurrentPage(), input.getSize());
        var list=new ArrayList<ProblemAbstract>();
        var res=new PageResult<ProblemAbstract>();
        var problems=oltProblem.findAllByGroupIdAndProblemTitleContainingOrderByDateDesc(input.getGroupId(),input.key,page);
        res.setSize(problems.getSize());
        res.setTotal(problems.getTotalElements());
        var problemsList=problems.getContent();
        for(var i :problemsList){
            var c= ProblemFactory.getInstance().getClassByType(i.getProblemType());
            var p=mongoTemplate.findById(i.getProblemId().toString(),c,i.getGroupId().toString());
            if(p!=null) {
                p.setProblem(i);
                list.add(p);
            }
        }
        res.setList(list);
        res.setError(ErrorEnum.Success_Http);
        return res;
    }


    public ProblemAbstract getProblemForEdit(String Id){
        var problem=oltProblem.getOne(Id);

        var c= ProblemFactory.getInstance().getClassByType(problem.getProblemType());
        if(c==null){
            return null;
        }
        return mongoTemplate.findById(problem.getProblemId().toString(),c,problem.getGroupId().toString());
    }

    public void deleteProblem(List<String> uuid,String groupId){
       oltProblem.deleteByProblemIdIn(uuid);
       List<String> uuids=new ArrayList<>();
       for(var i :uuid){
           uuids.add(i.toString());
       }
        Query query = Query.query(Criteria.where("_id").in(uuids));
        mongoTemplate.remove(query,groupId);


    }


    public Boolean saveOjProblem(ProgramProblem programProblem)  {
        try {
            var p = programProblem.getOjProblem();
            p.setSpj("0");
            p.setInDate(new Date());
            p.setTitle(programProblem.getProblem().getProblemTitle());
            p.setDefunct("N");
            var ftp=FtpUtil.getInstance();

            if(!ftp.ftpLogin()){
                return false;
            };
            var s = iojProblem.save(p);

            var path = ResourcePath.getResInstance().tempResource + File.separator + s.getProblemId();

            File f = new File(path);
            if (!f.exists() && !f.isDirectory()) {
                f.mkdirs();
            }
            ftp.deleteDir(s.getProblemId().toString());

            for (var i = 0; i < programProblem.getSimpleInput().size(); i++) {
                var in = path + File.separator + i + ".in";
                var out = path + File.separator + i + ".out";
                File inf=new File(in);
                inf.createNewFile();
                var w = new FileWriter(inf);
                w.write(programProblem.getSimpleInput().get(i));
                w.close();
                File outf=new File(out);
                outf.createNewFile();
                w = new FileWriter(out);
                w.write(programProblem.getSimpleOutput().get(i));
                w.close();

                ftp.uploadFile(s.getProblemId().toString(), inf.getName(),inf);
                ftp.uploadFile(s.getProblemId().toString(), outf.getName(),outf);


            }

            f.delete();



            return true;
        }
        catch (Exception e){
            e.printStackTrace();
            return  false;
        }



    }

    public Long pushSolution(SolutionInput input){
        var solution=new Solution();
        solution.setTime((long) 0);
        solution.setUserId("admin");
        solution.setCodeLength((long) 0);
        solution.setContestId(0);
        solution.setIp("localhost");
        solution.setMemory(0);
        solution.setLintError(0);
        solution.setJudger("admin");
        solution.setJudgetime(new Date());
        solution.setResult(0);
        solution.setPassRate(0.0);
        solution.setLanguage(input.getLanguage());
        solution.setValid(1);
        solution.setInDate(new Date());
        solution.setProblemId(input.getProblemId());
        solution.setNum(-1);
        solution=iSolution.save(solution);

        var source=new SourceCode();
        source.setSolutionId(solution.getSolutionId());
        source.setSource(input.getCode());


        iSourceCode.save(source);

        return solution.getSolutionId();


    }


    public SolutionResOutput getSolutionRes(Long id){
        var newSol=iSolution.getBySolutionId(id);

        while (newSol.getResult()==0) {

            newSol=iSolution.getBySolutionId(id);

        }
        SolutionResOutput solutionResOutput=new SolutionResOutput();
        SolutionRes solutionRes=new SolutionRes();
        solutionRes.setInDate(newSol.getInDate());
        solutionRes.setMemory(newSol.getMemory());
        solutionRes.setPassRate(newSol.getPassRate());
        solutionRes.setResult(newSol.getResult());
        solutionResOutput.setSolutionRes(solutionRes);

        var info=iRuntimeinfo.findFirstBySolutionId(id);

        var compileinfo=iCompileinfo.getBySolutionId(id);

        if(info==null){
            solutionResOutput.setRuntimeInfo("编译错误");
        }
        else {
            solutionResOutput.setCompileInfo(info.getError());
        }

        if(compileinfo!=null){
            solutionResOutput.setRuntimeInfo(compileinfo.getError());
        }


        return solutionResOutput;

    }


    public List<SelectDto<UUID>> getProblemGroupForSelect(Long userId){
        return problemGroup.findALlBYSelect(userId);
    }

    public Long saveExamPaper(SaveExamPaperInput input,Long userId){
        if(input.getExamId()==null){
            input.setExamId(IdUtil.getSnowflake(1,1).nextId());
        }
        else {
            iExamProblem.deleteByPaperId(input.getExamId());
        }

        ExamPaper examPaper=new ExamPaper();
        examPaper.setCreateUser(userId);
        examPaper.setCrateTime(new Date());
        examPaper.setPaperId(input.getExamId());
        examPaper.setPaperName(input.getExamTitles());
        iExamPaper.save(examPaper);
        for (var i =0;i<input.getProblems().size();i++){
            ExamProblem examProblem=new ExamProblem();
            examProblem.setExamPaperId(IdUtil.getSnowflake(1,1).nextId());
            examProblem.setPaperId(examPaper.getPaperId());
            examProblem.setNo(i);
            examProblem.setProblemId(input.getProblems().get(i).problemId);
            examProblem.setGroupId(input.getProblems().get(i).groupId);
            examProblem.setScore(input.getProblems().get(i).score);
            iExamProblem.save(examProblem);
        }

        return input.getExamId();
    }


    public Page<ExamPaper> getExamPaper(PageInput input,Long userId){
        var page= PageRequest.of(input.getCurrentPage(), input.getSize());
        return iExamPaper.findAllByCreateUserOrderByCrateTimeDesc(userId,page);
    }


    public ProblemForExamDto getProblemForExam(Long paperId){
        var examProblem=iExamProblem.findAllByPaperId(paperId);
        List<ProblemAbstract> problemAbstracts=new ArrayList<>();
        List<Integer> score=new ArrayList<>();
        for(var i :examProblem){
            var type=oltProblem.getProblemTypeByProblemId(i.getProblemId());
            var c= ProblemFactory.getInstance().getClassByType(type);
            var p=mongoTemplate.findById(i.getProblemId().toString(),c,i.getGroupId().toString());
            if(p!=null) {
                p.deleteAns();
                problemAbstracts.add(p);
            }
            score.add(i.getScore());
        }
        var ans=new ProblemForExamDto();
        ans.setIntegers(score);
        ans.setProblems(problemAbstracts);
        return ans;

    }


    public Page<OltTest> getTestByClass(GetTestInput input){
        var page= PageRequest.of(input.getCurrentPage(), input.getSize());
        return iOltTest.findAllByClassIdOrderByBeginTimeDesc(input.getClassId(),page);
    }

    public List<SelectDto<String>> getPaperForSelect(Long userId){

        var r= iExamPaper.getPaperForSelect(userId);
        var res=new ArrayList<SelectDto<String>>();
        for(var i:r){
            res.add(i.toStringSelect());
        }
        return res;

    }

    public Boolean saveOltTest(OltTest test){
        if(test.getTestId()==null){
            test.setTestId(IdUtil.objectId());
        }
        iOltTest.save(test);
        return true;
    }

    public OltTest getTestForEdit(String id){
        return iOltTest.getByTestId(id);
    }


    public String getTestSession(String testId,Long userId) throws Exception{
//        var test=iOltTest.getByTestId(testId);
//        var date=new Date();
//        if(test.getBeginTime().after(date)){
//            throw new Exception("考试未开始");
//        }
//        if(test.getEndTime().before(date)){
//            throw new Exception("考试已结束");
//        }
        var im= iUserScoreTotal.getByTestIdAndUserId(testId,userId);
        if(im!=null){
            throw  new Exception("已参加过考试，不能重复参加");
        }
        return SM2Util.getInstance().publicKeyHex;
    }

    public Boolean deleteTestById(String id) throws Exception{
        var im= iUserScoreTotal.existsByTestId(id);
        if(im){
            throw  new Exception("考试已有成绩不能删除");
        }
        iOltTest.deleteById(id);
        return true;
    }


    public HashMap<String,Integer> getScoreById (Long paperId){
        var s=iExamProblem.findAllByPaperId(paperId);
        HashMap<String,Integer> hashMap=new HashMap<>();
        for (var i:s){
            hashMap.put(i.getProblemId(),i.getScore());
        }
        return hashMap;
    }


    public Double getProgramScore(Integer score, JSONObject jsonObject){
        Long id=jsonObject.getLong("solutionId");
        var s=iSolution.getBySolutionId(id);
        if(s!=null&&s.getResult()==4){
            return score*1.0;
        }
        else {
            return 0.0;
        }

    }


    public ProblemAbstract getProblem(String problemId,String groupId,Integer problemType){
        var c=ProblemFactory.getInstance().getClassByType(problemType);
        return  mongoTemplate.findById(problemId,c,groupId);
    }


    public void saveStudentAns(OltProblem problem,Long userId,double score,StudentAns ans,Long paperId,String testId){
        UserExamScore userExamScore=new UserExamScore();
        userExamScore.setAns(ans.getAns());
        userExamScore.setPaperId(paperId);
        userExamScore.setUserId(userId);
        userExamScore.setScore(score);
        userExamScore.setProblemId(problem.getProblemId());
        userExamScore.setProblemGroupId(problem.getGroupId());
        userExamScore.setProblemType(problem.getProblemType());
        userExamScore.setScoreId(IdUtil.getSnowflake(1,1).nextIdStr());
        userExamScore.setTestId(testId);
        iUserExamScore.save(userExamScore);

    }


    public Boolean saveStudentUpload(UploadStudentAns ans,Long userId){
        var score=getScoreById(ans.getPaperId());
        double total=0.0;
        for(var item:ans.getStudentAns()){
            var p=getProblem(item.getProblemId(),item.getGroupId(),item.getProblemType());

            var json= JSON.parseObject(item.getAns());

            double itemScore=0.0;

            if(p.getProblem().getProblemType()==ProblemType.PROGRAM){
                itemScore=getProgramScore(score.get(item.getProblemId()),json);
            }
            else {
                itemScore=p.getScore(score.get(item.getProblemId()),json);
            }

            saveStudentAns(p.getProblem(),userId,itemScore,item,ans.getPaperId(),ans.getTestId());

            total+=itemScore;

        }
        UserScoreTotal userScoreTotal=new UserScoreTotal();
        userScoreTotal.setProblemTotal(ans.getStudentAns().size());
        userScoreTotal.setScoreTotal(total);
        userScoreTotal.setTestId(ans.getTestId());
        userScoreTotal.setUserId(userId);
        userScoreTotal.setTotalId(IdUtil.getSnowflake(1,1).nextIdStr());
        iUserScoreTotal.save(userScoreTotal);
        return  true;

    }

    public List<XYDto<Integer>> getScoreSegmented(String testId){
        List<XYDto<Integer>> res=new ArrayList<>();
        for (int i=0;i<=10;i++){

            XYDto<Integer> xyDto= new XYDto<>();
            Integer count=  iUserScoreTotal.countByTestIdAndScoreTotalGreaterThanEqualAndScoreTotalLessThan(testId, i*10, 10*(i+1));
            if(i==10){
              count+=res.get(i-1).getY();
              res.get(i-1).setY(count);
            }
            else {
                xyDto.setX((10*i) + "~" + (10 * (i + 1)));
                xyDto.setY(count);
                res.add(xyDto);
            }

        }
        return res;
    }
    public List<XYDto<Double>> getScoreMessage(String testId){
        var scores=iUserScoreTotal.findByTestIdOrderByScoreTotalDesc(testId);
        var sum=0.0;
        var sum2=0.0;
        var min=100.0;
        var max=0.0;
        var avg=0.0;
        var median=0.0;

        for(var i:scores){
            sum+=i.getScoreTotal();
            sum2+=Math.pow(i.getScoreTotal(),2);
            if(min>i.getScoreTotal()){
                min=i.getScoreTotal();
            }
            if(max<i.getScoreTotal()){
                max=i.getScoreTotal();
            }
        }
        if(scores.size()!=0) {

            if (scores.size() % 2 == 0) {
                median = (scores.get(scores.size() / 2).getScoreTotal() + scores.get((scores.size() / 2 - 1)).getScoreTotal()) / 2.0;
            } else {
                median = scores.get(scores.size() / 2).getScoreTotal();
            }


            avg = sum / scores.size();
            sum2 = sum2 / scores.size();
        }
        else {
            min=0;
        }
        List<XYDto<Double>> res=new ArrayList<>();
        res.add(new XYDto<>("最大值", max));
        res.add(new XYDto<>("最小值", min));
        res.add(new XYDto<>("平均值", avg));
        res.add(new XYDto<>("中位数", median));
        res.add(new XYDto<>("标准差",   Math.sqrt(sum2-Math.pow(avg,2))));
        return res;
    }

   public void getExcel(String testId, HttpServletResponse httpServletResponse){

       var writer = ExcelUtil.getWriter(true);

       writer.setSheet(0);
       writer.renameSheet(0,"成绩统计表");
       var scores=iUserScoreTotal.findByTestIdOrderByScoreTotalDesc(testId);
       var problems=iExamProblem.findByTestId(testId);
       var problemMaps=new HashMap<String,Integer>();

        List<String> titleRow=new ArrayList<>();
        List<List<String>> rows=new ArrayList<>();
        titleRow.add("编号");
        titleRow.add("姓名");
        for(int i=0;i<problems.size();i++){
            titleRow.add("第"+(i+1)+"题");
            problemMaps.put(problems.get(i).getProblemId(),problems.get(i).getNo());
        }
        titleRow.add("总计");
        rows.add(titleRow);
        for(var i:scores){
            var res=new String[titleRow.size()];
            var custom=iCustomDao.findByUserId(i.getUserId());
            var userscore=iUserExamScore.findByUserIdAndTestId(i.getUserId(),testId);
            res[0]=custom.getUserId().toString();
            res[1]=(custom.getName());
            for(var j:userscore){
                res[problemMaps.get(j.getProblemId())+2]=j.getScore().toString();
            }
            res[titleRow.size()-1]=i.getScoreTotal().toString();

            rows.add(Arrays.asList(res));

        }

        writer.write(rows, false);
       httpServletResponse.setContentType("application/octet-stream;charset=utf-8");
       httpServletResponse.setHeader("Content-disposition", "attachment;filename=" + "score" + ".xlsx");


        ServletOutputStream excelOut = null;
        try {
            excelOut = httpServletResponse.getOutputStream();
            writer.flush(excelOut,true);
            excelOut.close();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            writer.close();
        }


    }




}
